﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;

namespace NyxBot
{
    public enum SocketEvents
    {
        OnConnecting,
        OnConnect,
        OnDisconnect,
        OnDataArrival,
        OnDataSend,
        OnError
    }

    class clsSocket
    {
        // Events
        public delegate void SocketEventHandler(object sender, object[] data);
        public event SocketEventHandler OnConnecting;
        public event SocketEventHandler OnConnect;
        public event SocketEventHandler OnDisconnect;
        public event SocketEventHandler OnDataArrival;
        public event SocketEventHandler OnDataSend;
        public event SocketEventHandler OnError;

        // The goods
        public IPAddress address;
        public bool IsIPAddress = false;
        public int port;

        // Our stuff
        private Socket socket;
        private bool loop = false;

        public void _clsSocket(string raddress, int rport)
        {
            // Init our socket
            // Match addresses for some epic dns resolution
            Match match = Regex.Match(raddress, @"^([01]?\d\d|2[0-4]\d|25[0-5])\.([01]?\d\d|2[0-4]\d|25[0-5])\.([01]?\d\d|2[0-4]\d|25[0-5])\.([01]?\d\d|2[0-4]\d|25[0-5])$");
            if (match.Success)
            {
                // It's an IP addy
                address = IPAddress.Parse(raddress);
                IsIPAddress = true;
            } else {
                // Resolve the address
                try
                {
                    //address = Dns.GetHostEntry(address).AddressList.First();
                } catch (Exception e) {
                    if (OnError != null)
                        OnError(this, new object[] { e });
                }
            }
            
            port = rport;

            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);

            if (loop)
                AsyncSendLoop();
        }

        public clsSocket(string raddress, int rport)
        {
            loop = false;
            _clsSocket(raddress, rport);
        }

        public clsSocket(string raddress, int rport, bool rloop)
        {
            loop = rloop;
            _clsSocket(raddress, rport);
        }

        /// <summary>
        /// Connects to the remote host.
        /// </summary>
        /// <returns></returns>
        public bool Connect()
        {
            // TODO: process other data to see if we really want to connect

            // Disconnect any connections already open
            if (IsConnected())
                Disconnect();

            // raise OnConnecting
            if (OnConnecting != null)
                OnConnecting(this, new object[] {});

            // Connect
            try
            {
                socket.Connect(address, port);
            }
            catch (SocketException se)
            {
                // Socket error; raise OnError
                if (OnError != null)
                    OnError(this, new object[] { se });
                // Return failure
                return false;
            }

            // Raise OnConnect
            if (OnConnect != null)
                OnConnect(this, new object[] { address, port });

            // Return success
            return true;
        }

        /// <summary>
        /// Disconnects the remote connection.
        /// </summary>
        /// <returns></returns>
        public bool Disconnect()
        {
            // TODO: process other data to see if we really want to disconnect

            // Removing some redundancy
            if (!IsConnected())
                return false;

            // Disconnect
            try
            {
                socket.Disconnect(true);
            }
            catch (SocketException se)
            {
                // Socket error; raise OnError
                if (OnError != null)
                    OnError(this, new object[] { se });
                // Return failure
                return false;
            }

            // Raise OnDisconnect
            if (OnDisconnect != null)
                OnDisconnect(this, new object[] { });

            // Return success
            return true;
        }

        /// <summary>
        /// Sends data through the socket.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public bool SendData(byte[] data)
        {
            // Send the data through the socket
            try
            {
                socket.Send(data);
            }
            catch (SocketException se)
            {
                // Socket error; raise OnError
                if (OnError != null)
                    OnError(this, new object[] { se });
                // Return failure
                return false;
            }

            // Raise OnDisconnect
            if (OnDataSend != null)
                OnDataSend(this, new object[] { data });

            // Return success
            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private byte[] GetData()
        {
            // One set, no blocking
            try
            {
                byte[] data = new byte[2048]; //2kb, about as big as the motd
                socket.Receive(data);
                // Raise OnDataArrival
                if (OnDataArrival != null)
                    OnDataArrival(this, new object[] { data });
                return data;
            }
            catch (SocketException se)
            {
                // Socket error; raise OnError
                if (OnError != null)
                    OnError(this, new object[] { se });
                // Return failure
                return new byte[0];
            }
        }

        /// <summary>
        /// Determines if the socket is connected to the remote host.
        /// </summary>
        /// <returns></returns>
        public bool IsConnected()
        {
            if (socket.Connected)
                return true;
            return false;
        }

        /// <summary>
        /// Returns the remote EndPoint of the socket.
        /// </summary>
        /// <returns></returns>
        public EndPoint GetEndPoint()
        {
            return socket.RemoteEndPoint;
        }

        /// <summary>
        /// 
        /// </summary>
        private void AsyncSendLoop()
        {
            while (true)
            {
                GetData();
                System.Threading.Thread.Sleep(10);
            }
        }
    }
}
